package yartiss.service.viewmodel;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.filechooser.FileFilter;

import yartiss.engine.simulation.IEnergyConsumptionProfile;
import yartiss.engine.simulation.IEnergyProfile;
import yartiss.engine.simulation.ISchedulingPolicy;
import yartiss.engine.simulation.Simulation;
import yartiss.engine.simulation.SimulationParameters;
import yartiss.engine.simulation.energy.profiles.EnergyProfileFactory;
import yartiss.engine.simulation.energy.profiles.consumption.ConsumptionProfileFactory;
import yartiss.engine.simulation.io.DefaultParamsParser;
import yartiss.engine.simulation.scheduling.policies.SchedulingPolicyFactory;
import yartiss.engine.simulation.system.ITask;
import yartiss.engine.simulation.system.ITaskSet;
import yartiss.engine.simulation.system.SystemMetrics;
import yartiss.engine.simulation.system.taskset.generation.ITaskSetGenerator;
import yartiss.engine.simulation.system.taskset.generation.UUniFastDiscardTaskSetGen;
import yartiss.engine.simulation.system.taskset.generation.task.DefaultTaskGenerator;
import yartiss.framework.view.AbstractView;
import yartiss.framework.view.IView;
import yartiss.framework.viewmodel.AbstractViewModel;
import yartiss.service.model.DefaultEventsModel;
import yartiss.service.model.IEventsModel;
import yartiss.service.model.TasksTableModel;

public class TimeLineViewModel extends AbstractViewModel{

	private AbstractView view;
	private IEventsModel eventsModel;
	private int duration = 1000;
	private String state ="";
	private double averageIdlePeriod;
	private double averageBusyPeriod;
	private double timeAtEmax;
	private double timeAtTmin;
	private double end;
	private int nbProc = 1;
	private int E0 = 50, Emax=50, Emin=0,Pr=5;
	private int count;
	long busyPeriod;
	long hyperPeriod;
	private double utilization, energyUtilization;
	private String selectedPolicyName = "FP";
	private String selectedConsumptionProfile=ConsumptionProfileFactory.getAvailableProfiles()[0];
	private boolean isGlobalConsumption = false;
	private boolean isEnergyAware = false;

	private String policyParameters = "";
	private List<Integer> policyParams = null;
	private final TasksTableModel tasksModel = new TasksTableModel();
	private boolean isContentChanged = true;
	
	public TimeLineViewModel(){
		tasksModel.addTableModelListener(new TableModelListener() {
			@Override
			public void tableChanged(TableModelEvent e) {
				setCount(tasksModel.getRowCount());
				List<ITask> allTasks = tasksModel.getAllTasks();
				setUtilization(Math.round(SystemMetrics.utilisation(allTasks)*10000)/10000.0d);
				setHyperPeriod(SystemMetrics.hyperPeriod(allTasks));
				setEnergyUtilization(Math.round(SystemMetrics.energyUtilization(allTasks,Pr)*10000)/10000.0d);
				setBusyPeriod(SystemMetrics.FPBusyPeriod(allTasks));
				setIsContentChanged(true);
			}
		});
		tasksModel.addRow("simple", 0, 2, 8, 8, 3, 1);
		tasksModel.addRow("simple",0, 2, 10, 10, 9, 2);
		tasksModel.addRow("simple",0, 2, 10, 18, 17, 3);
		tasksModel.addRow("simple",0, 2, 10, 18, 17, 6);
		
//		tasksModel.addRow(0, 1, 1, 2, 2, 1);
//		tasksModel.addRow(0, 1, 1, 4, 4, 2);
//		tasksModel.addRow(0, 1, 1, 8, 8, 3);
	}

	@Override
	public IView getView() {
		return view;
	}

	/**
	 * @param view the view to set
	 */
	public void setView(AbstractView view) {
		unregisterViewListeners();
		this.view = view;
		view.setDataContext(this);
		registerViewListeners();
	}
	
	private void unregisterViewListeners(){
		if(view != null)
			view.unregisterPropertyChangeListener(listener);
	}
	
	private void registerViewListeners(){
		if(view != null)
			view.registerPropertyChangeListerner(listener);
	}
	
	private PropertyChangeListener listener = new PropertyChangeListener() {
		@Override	public void propertyChange(PropertyChangeEvent evt) {
			Object value = view.getProperty(evt.getPropertyName());
			setProperty(evt.getPropertyName(), value);
			setIsContentChanged(true);
		}
	};
	
	public IEventsModel getEventsModel(){
		return eventsModel;
	}
	
	public void setEventsModel(IEventsModel model){
		IEventsModel old = eventsModel;
		eventsModel = model ;
		firePropertyChanged("EventsModel", old, eventsModel);
	}
	
	/**
	 * @return the duration
	 */
	public int getDuration() {
		return duration;
	}

	/**
	 * @param duration the duration to set
	 */
	public void setDuration(int duration) {
		this.duration = duration;
		firePropertyChanged("Duration", duration, duration);
	}

	/**
	 * @return the state
	 */
	public String getState() {
		return state;
	}

	/**
	 * @param state the state to set
	 */
	public void setState(String state) {
		this.state = state;
		firePropertyChanged("State", state, state);
	}

	/**
	 * @return the averageIdlePeriod
	 */
	public double getAverageIdlePeriod() {
		return averageIdlePeriod;
	}

	/**
	 * @param averageIdlePeriod the averageIdlePeriod to set
	 */
	public void setAverageIdlePeriod(double averageIdlePeriod) {
		this.averageIdlePeriod = averageIdlePeriod;
		firePropertyChanged("AverageIdlePeriod", averageIdlePeriod, averageIdlePeriod);
	}

	/**
	 * @return the averageBusyPeriod
	 */
	public double getAverageBusyPeriod() {
		return averageBusyPeriod;
	}

	/**
	 * @param averageBusyPeriod the averageBusyPeriod to set
	 */
	public void setAverageBusyPeriod(double averageBusyPeriod) {
		this.averageBusyPeriod = averageBusyPeriod;
		firePropertyChanged("AverageBusyPeriod", averageBusyPeriod, averageBusyPeriod);
	}

	/**
	 * @return the timeAtEmax
	 */
	public double getTimeAtEmax() {
		return timeAtEmax;
	}

	/**
	 * @param timeAtEmax the timeAtEmax to set
	 */
	public void setTimeAtEmax(double timeAtEmax) {
		this.timeAtEmax = timeAtEmax;
		firePropertyChanged("TimeAtEmax", timeAtEmax, timeAtEmax);
	}

	/**
	 * @return the timeAtTmin
	 */
	public double getTimeAtTmin() {
		return timeAtTmin;
	}

	/**
	 * @param timeAtTmin the timeAtTmin to set
	 */
	public void setTimeAtTmin(double timeAtTmin) {
		this.timeAtTmin = timeAtTmin;
		firePropertyChanged("TimeAtTmin", timeAtTmin, timeAtTmin);
	}

	/**
	 * @return the end
	 */
	public double getEnd() {
		return end;
	}

	/**
	 * @param end the end to set
	 */
	public void setEnd(double end) {
		this.end = end;
		firePropertyChanged("End", end, end);
	}

	/**
	 * @return the e0
	 */
	public int getE0() {
		return E0;
	}

	/**
	 * @param e0 the e0 to set
	 */
	public void setE0(int e0) {
		E0 = e0;
		firePropertyChanged("E0", e0, e0);
	}

	/**
	 * @return the emax
	 */
	public int getEmax() {
		return Emax;
	}

	/**
	 * @param emax the emax to set
	 */
	public void setEmax(int emax) {
		Emax = emax;
		firePropertyChanged("Emax", emax, emax);
	}

	/**
	 * @return the emin
	 */
	public int getEmin() {
		return Emin;
	}

	/**
	 * @param emin the emin to set
	 */
	public void setEmin(int emin) {
		Emin = emin;
		firePropertyChanged("Emin", Emin, Emin);
	}

	/**
	 * @return the pr
	 */
	public int getPr() {
		return Pr;
	}

	/**
	 * @param pr the pr to set
	 */
	public void setPr(int pr) {
		Pr = pr;
		firePropertyChanged("Pr", pr, pr);
		setEnergyUtilization(Math.round(SystemMetrics.energyUtilization( tasksModel.getAllTasks(),Pr)*100)/100.0d);
	}

	/**
	 * @return the count
	 */
	public int getCount() {
		return count;
	}

	/**
	 * @param count the count to set
	 */
	public void setCount(int count) {
		this.count = count;
		firePropertyChanged("Count", count, count);
	}

	/**
	 * @return the busyPeriod
	 */
	public long getBusyPeriod() {
		return busyPeriod;
	}

	/**
	 * @param bp the busyPeriod to set
	 */
	public void setBusyPeriod(long bp) {
		this.busyPeriod = bp;
		firePropertyChanged("BusyPeriod", bp, bp);
	}

	/**
	 * @return the hyperPeriod
	 */
	public long getHyperPeriod() {
		return hyperPeriod;
	}

	/**
	 * @param hp the hyperPeriod to set
	 */
	public void setHyperPeriod(long hp) {
		this.hyperPeriod = hp;
		firePropertyChanged("HyperPeriod", hp, hp);
	}

	/**
	 * @return the utilization
	 */
	public double getUtilization() {
		return utilization;
	}

	/**
	 * @param utilization the utilization to set
	 */
	public void setUtilization(double utilization) {
		this.utilization = utilization;
		firePropertyChanged("Utilization", utilization, utilization);
	}
	
	/**
	 * @return the energyUtilization
	 */
	public double getEnergyUtilization() {
		return energyUtilization;
	}

	/**
	 * @param energyUtilization the energyUtilization to set
	 */
	public void setEnergyUtilization(double energyUtilization) {
		this.energyUtilization = energyUtilization;
		firePropertyChanged("EnergyUtilization", energyUtilization, energyUtilization);
	}

	/**
	 * @return the vailibalePolicies
	 */
	public String [] getAvailablePolicies() {
		return SchedulingPolicyFactory.getAvailableSchedulingPoliciesNames();
	}
	
	public String [] getAvailibaleConsumptionProfiles() {
		return ConsumptionProfileFactory.getAvailableProfiles();
	}

	/**
	 * @return the policyParameters
	 */
	public String getPolicyParameters() {
		return policyParameters;
	}

	/**
	 * @param policyParameters the policyParameters to set
	 */
	public void setPolicyParameters(String policyParameters) {
		this.policyParameters = policyParameters;
		policyParams = parsePolicyParams(policyParameters);
		firePropertyChanged("PolicyParameters", policyParameters, policyParameters);
	}
	
	/**
	 * @return the nbProc
	 */
	public int getNbProc() {
		return nbProc;
		
	}

	/**
	 * @param nbProc the nbProc to set
	 */
	public void setNbProc(int nbProc) {
		this.nbProc = nbProc;
		firePropertyChanged("NbProc", nbProc, nbProc);
	}
	
	

	/**
	 * @return the selectedPolicyName
	 */
	public String getSelectedPolicyName() {
		return selectedPolicyName;
	}

	/**
	 * @param selectedPolicyName the selectedPolicyName to set
	 */
	public void setSelectedPolicyName(String selectedPolicyName) {
		this.selectedPolicyName = selectedPolicyName;
		firePropertyChanged("SelectedPolicyName", selectedPolicyName, selectedPolicyName);
	}
	
	
	/**
	 * @return the selectedPolicyName
	 */
	public String getSelectedConsumptionProfile() {
		return selectedConsumptionProfile;
	}

	/**
	 * @param selectedPolicyName the selectedPolicyName to set
	 */
	public void setSelectedConsumptionProfile(String selectedConsumptionProfile) {
		this.selectedConsumptionProfile = selectedConsumptionProfile;
		firePropertyChanged("SelectedConsumptionProfile", selectedConsumptionProfile, selectedConsumptionProfile);
	}

	/**
	 * @return the tasksModel
	 */
	public TasksTableModel getTasksModel() {
		return tasksModel;
	}
	
	/**
	 * @return the generateCommand
	 */
	public ActionListener getGenerateCommand() {
		return generateCommand;
	}

	private final ActionListener generateCommand = new ActionListener() {
		@Override public void actionPerformed(ActionEvent e) {
			int nbTasks = getCount()>0 ? getCount(): 5;
			double U = getUtilization() >0 ? getUtilization() : 0.8;
			double Ue = getEnergyUtilization()>0 ? getEnergyUtilization() : 0.8;
			ITaskSetGenerator generator = new  UUniFastDiscardTaskSetGen(new DefaultTaskGenerator(), nbTasks,1, U, Ue, Pr,Emax, 0, false, true);
			ITask[] tasks = generator.generate();
			tasksModel.clear();
			tasksModel.addAll(tasks);
		}
	};
	
	private static List<Integer> parsePolicyParams(String params){
		String [] tokens = params.split(" ");
		List<Integer> numbers = new ArrayList<Integer>();
		for (String token : tokens) {
			try {
				numbers.add( Integer.parseInt(token));
			}
			catch (Exception e) {}
		}
		return numbers;
	}

	private SimulationParameters buildSimulationParameters(){
		ISchedulingPolicy policy = SchedulingPolicyFactory.getInstance(selectedPolicyName);
		policy.setParameters(policyParams);
		
		IEnergyProfile energyProfile = isEnergyAware ? EnergyProfileFactory.getInstance("default",Pr, Emax, Emin, E0 ) : EnergyProfileFactory.getInstance("default",Integer.MAX_VALUE, Integer.MAX_VALUE, 0, Integer.MAX_VALUE );
		IEnergyConsumptionProfile consumptionProfile = ConsumptionProfileFactory.newIntance(selectedConsumptionProfile);
		energyProfile.setGlobalEnergyConsumptionProfile(consumptionProfile);
		energyProfile.setGolobalConsumptionProfileEnabled(isGlobalConsumption);
		
		ITaskSet taskSet = policy.createTaskSet();
		for (ITask task : tasksModel.getAllTasks()) {
			taskSet.addTask(task.cloneTask());
		}
		return new SimulationParameters(policy, duration, energyProfile, taskSet, getNbProc(), isEnergyAware); 
	}
	
	/**
	 * @return the startSimulationCommand
	 */
	public ActionListener getStartSimulationCommand() {
		return startSimulationCommand;
	}

	/**
	 * @return the isContentChanged
	 */
	public boolean getIsContentChanged() {
		return isContentChanged;
	}

	/**
	 * @param isContentChanged the isContentChanged to set
	 */
	public void setIsContentChanged(boolean isContentChanged) {
		this.isContentChanged = isContentChanged;
		firePropertyChanged("IsContentChanged", isContentChanged, isContentChanged);
	}
	
	/**
	 * @return the isGlobalConsumption
	 */
	public boolean getIsGlobalConsumption() {
		return isGlobalConsumption;
	}

	/**
	 * @param isGlobalConsumption the isGlobalConsumption to set
	 */
	public void setIsGlobalConsumption(boolean isGlobalConsumption) {
		this.isGlobalConsumption = isGlobalConsumption;
		tasksModel.setConsProfileVisible(!isGlobalConsumption);
		firePropertyChanged("IsGlobalConsumption", isGlobalConsumption, isGlobalConsumption);
	}
	
	/**
	 * @return the isEnergyAware
	 */
	public boolean getIsEnergyAware() {
		return isEnergyAware;
	}

	/**
	 * @param isEnergyAware the isEnergyAware to set
	 */
	public void setIsEnergyAware(boolean isEnergyAware) {
		this.isEnergyAware = isEnergyAware;
		tasksModel.setEnergyCostVisivble(isEnergyAware);
		firePropertyChanged("IsEnergyAware", isEnergyAware, isEnergyAware);
	}

	/**
	 * @return the loadCommand
	 */
	public ActionListener getLoadCommand() {
		return loadCommand;
	}
	
	public ActionListener getSaveCommand() {
		return saveCommand;
	}

	private final ActionListener startSimulationCommand = new ActionListener() {
		@Override	public void actionPerformed(ActionEvent e) {
			setIsContentChanged(false);
			new Thread(new Runnable() {
			      public void run() {
			        SwingUtilities.invokeLater(new Runnable() {
			          public void run() {
			        	Simulation sim = new Simulation(buildSimulationParameters());
			  			setEventsModel(new DefaultEventsModel(sim));
			  			sim.simulate();
			  			setEnd(sim.getStatistics().getSimulationDuration());
			  			setState(sim.getStatistics().isFailed() ? "Failed": "Successful");
			  			setAverageBusyPeriod(sim.getStatistics().getAverageBusyPeriod());
			  			setAverageIdlePeriod(sim.getStatistics().getAverageIdlePeriod());
			  			setTimeAtEmax(sim.getStatistics().getTimePassedWithEmax());
			  			setTimeAtTmin(sim.getStatistics().getTimePassedWithEmin());
			          }
			        });
			      }
			  }).start();
			
		}
	};
	
	private void updateUIFromParameters(SimulationParameters params){
		setDuration((int) params.getSimulationEnd());
		setSelectedPolicyName(params.getSchedulingPolicy().getPolicyName());
		String s ="";
		if(params.getSchedulingPolicy().getParameters() != null)
			for (int param : params.getSchedulingPolicy().getParameters()) s += " "+param;
		setPolicyParameters(s);
		setE0((int) params.getEnergyProfile().getStartEnergyLevel());
		setEmax((int) params.getEnergyProfile().getMaxEnergyLevel());
		setEmin((int) params.getEnergyProfile().getMinEnergyLevel());
		setPr((int) params.getEnergyProfile().getPowerRate());
		setNbProc(params.getNbProc());
		tasksModel.clear();
		for (ITask task : params.getTaskSet()) {
			tasksModel.addRow(task);
		}
	}

	

	

	private String fileName = "system.rtss";
	private final ActionListener loadCommand = new ActionListener() {
		@Override	public void actionPerformed(ActionEvent e) {
			JFileChooser fileChooser = new JFileChooser(fileName);
			fileChooser.setFileFilter(new FileFilter() {
				@Override
				public String getDescription() {
					return "RTSS simulator file";
				}
				
				@Override
				public boolean accept(File f) {
					return true;
				}
			});
			int result = fileChooser.showOpenDialog(view);
			if (result == JFileChooser.APPROVE_OPTION) {
				File file = fileChooser.getSelectedFile();
				fileName = file.getPath();
				try {
					SimulationParameters params = DefaultParamsParser.parseXml(new FileInputStream(file));
					updateUIFromParameters(params);
				} catch (Exception e1) {
					e1.printStackTrace();
				}
			}
		}
	};
	
	private final ActionListener saveCommand = new ActionListener() {
		@Override	public void actionPerformed(ActionEvent e) {
			JFileChooser fileChooser = new JFileChooser(fileName);
			fileChooser.setFileFilter(new FileFilter() {
				@Override
				public String getDescription() {
					return "RTSS simulator file";
				}
				
				@Override
				public boolean accept(File f) {
					return true;
				}
			});
			int result = fileChooser.showSaveDialog(view);
			if (result == JFileChooser.APPROVE_OPTION) {
				File file = fileChooser.getSelectedFile();
				fileName = file.getPath();
				try {
					SimulationParameters params = buildSimulationParameters();
					DefaultParamsParser.write(params, fileName);
				} catch (Exception e1) {
					e1.printStackTrace();
				}
			}
		}
	};
}
